home *** CD-ROM | disk | FTP | other *** search
/ MIDICraft's MIDINET CD-ROM / MIDICraft's MIDINET CD-ROM.iso / DOSUTILS / MIDIFIX.ZIP / MIDIFIX.CPP next >
C/C++ Source or Header  |  1997-01-11  |  16KB  |  742 lines

  1. // midifix v1.3 written by Günter Nagler 1995 (gnagler@ihm.tu-graz.ac.at)
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <stdlib.h>
  5.  
  6. #ifdef __MSDOS__
  7. #define READWRITE_BINARY  "r+b"
  8. #define CREATE_READWRITE_BINARY  "w+b"
  9. #define WRITE_BINARY  "wb"
  10. #define READ_BINARY   "rb"
  11. #define STRICMP           stricmp
  12. #else
  13. #define READWRITE_BINARY  "r+"
  14. #define CREATE_READWRITE_BINARY  "w+"
  15. #define WRITE_BINARY  "w"
  16. #define READ_BINARY   "r"
  17. #define STRICMP           strcmp
  18. #endif
  19.  
  20. // uncomment this define if you want to fix channel errors
  21. //#define FIX_CHANNELERROR
  22.  
  23. #define DEFAULT_OUTPUTNAME "midifix.mid"
  24. #define MAXEVENTSIZE 1024
  25.  
  26. long filesize = 0;
  27. char* name = 0;
  28. char* outputname = 0;
  29. char* extramidi = "extra%d.mid";
  30. int version = 0;
  31. int tracksonly = 0;
  32.  
  33. void copybytes(FILE* df, FILE* sf, long n)
  34. {
  35.   while (n-- > 0)
  36.   {
  37.     int c = fgetc(sf);
  38.     if (c < 0)
  39.     {
  40.       perror(name);
  41.       exit(1);
  42.     }
  43.     else
  44.       fputc(c, df);
  45.   }
  46. }
  47.  
  48. long getlong(FILE* f)
  49. {
  50. unsigned char v[4];
  51. long len;
  52.  
  53.   if (fread(v, 1, 4, f) != 4)
  54.     return -1;
  55.   len = v[0];
  56.   len = (len << 8) + v[1];
  57.   len = (len << 8) + v[2];
  58.   len = (len << 8) + v[3];
  59.   return len;
  60. }
  61.  
  62. void putlong(FILE* f, long len)
  63. {
  64. unsigned char v[4];
  65.  
  66.   v[3] = len & 0xff; len >>= 8;
  67.   v[2] = len & 0xff; len >>= 8;
  68.   v[1] = len & 0xff; len >>= 8;
  69.   v[0] = len & 0xff;
  70.   if (fwrite(v, 1, 4, f) != 4)
  71.   {
  72.     perror(name);
  73.     exit(1);
  74.   }
  75. }
  76.  
  77. unsigned int getword(FILE* f)
  78. {
  79. unsigned char v[2];
  80. unsigned int len = 0;
  81.  
  82.   if (fread(v, 1, 2, f) == 2)
  83.   {
  84.     len = v[0];
  85.     len = (len << 8) + v[1];
  86.   }
  87.   return len;
  88. }
  89.  
  90. void putword(FILE* f, int len)
  91. {
  92. unsigned char v[2];
  93.  
  94.   v[1] = len & 0xff; len >>= 8;
  95.   v[0] = len & 0xff;
  96.   if (fwrite(v, 1, 2, f) != 2)
  97.   {
  98.     perror(name);
  99.     exit(1);
  100.   }
  101. }
  102.  
  103. long findbytes(FILE* f, unsigned char *s, int len)
  104. {
  105. long oldpos = ftell(f);
  106. long pos = -1, markpos;
  107. int c, matchlen;
  108.  
  109.   if (s && len > 0)
  110.   {
  111.     while ((c = fgetc(f)) != EOF)
  112.     {
  113.       if (c == *s)
  114.       {
  115.     markpos = ftell(f);
  116.     matchlen = 1;
  117.     while (matchlen < len)
  118.     {
  119.       c = fgetc(f);
  120.       if (c == EOF)
  121.         break;
  122.       if (c == s[matchlen])
  123.         matchlen++;
  124.       else
  125.         break;
  126.     }
  127.     if (matchlen == len)
  128.     {
  129.       pos = markpos-1;
  130.       break;
  131.     }
  132.     else
  133.       fseek(f, markpos, SEEK_SET);
  134.       }
  135.     }
  136.   }
  137.   fseek(f, oldpos, SEEK_SET);
  138.   return pos;
  139. }
  140.  
  141. unsigned char mthd[] = "MThd";
  142. unsigned char mtrk[] = "MTrk";
  143. unsigned char snio[] = "SNio";
  144.  
  145. // find MTrk
  146. long findtrack(FILE* f)
  147. {
  148.   return findbytes(f, mtrk, 4);
  149. }
  150.  
  151. // find header tag
  152. long findmidi(FILE* f)
  153. {
  154.   long pos = findbytes(f, mthd, 4);
  155.   if (pos < 0)
  156.     pos = findbytes(f, snio, 4); // taiwan midi format?
  157.   return pos;
  158. }
  159.  
  160. int IsTag(unsigned char* buf, unsigned char* tag)
  161. {
  162.   return memcmp(buf, tag, 4) == 0;
  163. }
  164.  
  165. int IsHeaderTag(unsigned char* tag)
  166. {
  167.   return IsTag(tag, mthd) || IsTag(tag, snio);
  168. }
  169.  
  170. int IsTrackTag(unsigned char* tag)
  171. {
  172.   return IsTag(tag, mtrk);
  173. }
  174.  
  175. long findtrackend(FILE* f)
  176. {
  177. unsigned char endtrk[] = { 0xff, 0x2f, 0x00 };
  178. long endpos;
  179.  
  180.   endpos = findbytes(f, endtrk, 3);
  181.   if (endpos > 0)
  182.     return endpos+3;
  183.   return endpos;
  184. }
  185.  
  186. void updatetrackcount(FILE*f, int count)
  187. {
  188. long oldpos = ftell(f);
  189.  
  190.   fseek(f, 10, SEEK_SET);
  191.   putword(f, count);
  192.   printf("number of tracks updated: %d\n", count);
  193.   fseek(f, oldpos, SEEK_SET);
  194. }
  195.  
  196. int get(FILE* f, long &len, unsigned char* event, int & n, int count)
  197. {
  198.   if (n + count > MAXEVENTSIZE)
  199.   {
  200.     fprintf(stderr, "overflow\n");
  201.     return 0;
  202.   }
  203.   while (count > 0 && len > 0)
  204.   {
  205.     int c = fgetc(f);
  206.     if (c < 0)
  207.       break;
  208.     event[n++] = c;
  209.     count--;
  210.     len--;
  211.   }
  212.   if (count > 0)
  213.   {
  214.     printf( "removing incomplete midi command at %08lX\n", ftell(f)-n);
  215.     return 0;
  216.   }
  217.   return 1;
  218. }
  219.  
  220. int getdelta(FILE* f, long &len, unsigned char* event, int & n, long &value)
  221. {
  222.   value = 0;
  223.   for (int i = 0; i < 4; i++)
  224.   {
  225.     if (!get(f, len, event, n, 1))
  226.       return 0;
  227.     value = (value << 7) + (event[n-1] & 0x7f);
  228.     if ((event[n-1] & 0x80) == 0)
  229.       break;
  230.   }
  231.   return 1;
  232. }
  233.  
  234. #define NOTCHANGED   0
  235. #define CHANGED     1
  236. #define NOTREPAIRABLE -1
  237. int copytrack(FILE* of, FILE* f, long len)
  238. {
  239.   if (tracksonly)
  240.   {
  241.     // copy track data without checking content
  242.     copybytes(of, f, len);
  243.     return NOTCHANGED;
  244.   }
  245. int lastcode = -1;
  246. int endfound = 0;
  247. static unsigned char event[MAXEVENTSIZE], *cmd, *param;
  248. int n = 0;
  249. int midicode = -1;
  250. int channel = -1;
  251. int channelerror = 0;
  252. int repairedcmd = 0;
  253.  
  254.   while (len > 0)
  255.   {
  256.   long duration;
  257.  
  258.      n = 0;
  259.      // time code
  260.      if (!getdelta(f, len, event, n, duration))
  261.        goto stoptrack;
  262.      cmd = event + n;
  263.      if (!get(f, len, event, n, 1))
  264.        goto stoptrack;
  265.      if (*cmd >= 0x80 || lastcode < 0)
  266.      {
  267.        midicode = *cmd;
  268.      }
  269.      else
  270.      {
  271.        fseek(f, -1, SEEK_CUR);
  272.        midicode = lastcode;
  273.        n--;
  274.        len++;
  275.      }
  276.      param = event+n;
  277.      endfound = 0;
  278.      // check command
  279.      switch(midicode)
  280.      {
  281.      case 0xf0: // sysex
  282.        {
  283.      while (len > 0 && n < sizeof(event) - 1)
  284.      {
  285.        if (!get(f, len, event, n, 1))
  286.          goto stoptrack;
  287.        if (event[n-1] == 0xF7) // end of sysex
  288.          break;
  289.      }
  290.      if (event[n-1] != 0xF7)
  291.      {
  292.          printf( "remove incomplete sysex command\n");
  293.          goto stoptrack;
  294.      }
  295.        }
  296.        break;
  297.      case 0xf2:
  298.      {
  299.        if (!get(f, len, event, n, 2))
  300.      goto stoptrack;
  301.        break;
  302.      }
  303.      case 0xf3:
  304.        if (!get(f, len, event, n, 1))
  305.      goto stoptrack;
  306.        break;
  307.      case 0xf6:
  308.      case 0xf8:
  309.      case 0xfa:
  310.      case 0xfb:
  311.      case 0xfc:
  312.      case 0xfe:
  313.        break;
  314.      case 0xff:
  315.        {
  316.      if (!get(f, len, event, n, 1))
  317.        goto stoptrack;
  318.      long metalen;
  319.      if (!getdelta(f, len, event, n, metalen))
  320.        goto stoptrack;
  321.      if (len < metalen)
  322.        goto stoptrack;
  323.      if (n + metalen > MAXEVENTSIZE)
  324.      {
  325.        fseek(f, -n, SEEK_CUR);
  326.        copybytes(of, f, n+metalen);
  327.        len -= metalen;
  328.        n = 0;
  329.      }
  330.      else
  331.      {
  332.        if (!get(f, len, event, n, (int)metalen))
  333.          goto stoptrack;
  334.      }
  335.      switch(param[0])
  336.      {
  337.        case 0x2F:
  338.          endfound = 1;
  339.          break;
  340.      }
  341.        }
  342.        break;
  343.      default:
  344.        {
  345.      if (midicode < 0x80)
  346.      {
  347.        printf( "removing invalid midi command at %08lX\n", ftell(f)-1);
  348.        goto stoptrack;
  349.      }
  350.      if (version == 1 && midicode < 0xf0)
  351.      {
  352.        if (channel >= 0)
  353.        {
  354.          if (channel != (midicode & 0x0F))
  355.          {
  356.            if (!channelerror)
  357.            {
  358.          printf( "Warning: version 1 track contains different channel commands\n");
  359. #ifdef FIX_CHANNELERROR)
  360.            printf( "setting channel to %d\n", channel+1);
  361. #endif
  362.            }
  363. #ifdef FIX_CHANNELERROR)
  364.          midicode = midicode & 0xF0 + channel; // change channel
  365. #endif
  366.            channelerror = 1;
  367.          }
  368.        }
  369.        else
  370.          channel = midicode & 0x0F;
  371.      }
  372.      switch(midicode & 0xF0)
  373.      {
  374.      case 0x80:
  375.      case 0x90:
  376.        {
  377.          lastcode = midicode;
  378.  
  379.          if (!get(f, len, event, n, 2))
  380.            goto stoptrack;
  381.          if (param[0] >= 0x80 || param[1] >= 0x80)
  382.          {
  383.            printf( "removing invalid note command at %08lX\n", ftell(f)-3);
  384.            goto stoptrack;
  385.          }
  386.        }
  387.        break;
  388.      case 0xA0:
  389.        {
  390.          if (!get(f, len, event, n, 2))
  391.            goto stoptrack;
  392.  
  393.          lastcode = midicode;
  394.        }
  395.        break;
  396.      case 0xB0:
  397.        {
  398.          if (!get(f, len, event, n, 2))
  399.            goto stoptrack;
  400.  
  401.          if (param[1] >= 0x80)
  402.          {
  403.            printf( "invalid control value used at %08lX\n", ftell(f)-3);
  404.            param[1] = 0x7f;
  405.            repairedcmd = 1;
  406.          }
  407.          lastcode = midicode;
  408.        }
  409.        break;
  410.      case 0xC0:
  411.        {
  412.          if (!get(f, len, event, n, 1))
  413.            goto stoptrack;
  414.  
  415.          if (param[0] >= 0x80)
  416.          {
  417.            printf( "invalid program number changed at %08lX\n", ftell(f)-2);
  418.            param[0] = 0;
  419.            repairedcmd = 1;
  420.          }
  421.          lastcode = midicode;
  422.        }
  423.        break;
  424.      case 0xD0:
  425.        {
  426.          if (!get(f, len, event, n, 1))
  427.            goto stoptrack;
  428.          if (param[0] >= 0x80)
  429.          {
  430.            printf( "invalid aftertouch command changed at %08lX\n", ftell(f)-2);
  431.            param[0] = 0x7f;
  432.            repairedcmd = 1;
  433.          }
  434.          lastcode = midicode;
  435.        }
  436.        break;
  437.      case 0xE0:
  438.        {
  439.          if (!get(f, len, event, n, 2))
  440.            goto stoptrack;
  441.  
  442.          lastcode = midicode;
  443.        }
  444.        break;
  445.      default:
  446.        {
  447.          printf( "removing invalid command %02X at %08lX\n", midicode, ftell(f)-1);
  448.          goto stoptrack;
  449.        }
  450.      }
  451.        }
  452.        break;
  453.      }
  454.      if (n > 0)
  455.      {
  456.        if (fwrite(event, 1, n, of) != n)
  457.        {
  458.      perror(outputname);
  459.      return NOTREPAIRABLE;  // disk full
  460.        }
  461.        n = 0;
  462.      }
  463.   }
  464.   if (endfound && len == 0 && !repairedcmd
  465. #ifdef FIX_CHANNELERROR)
  466.       && !channelerror
  467. #endif
  468.     )
  469.     return NOTCHANGED;
  470. stoptrack:
  471.   len += n;
  472.   if (len > 0)
  473.     printf( "Warning: %ld bytes lost.\n", len);
  474.   if (!endfound)
  475.   {
  476.     if (fwrite("\0\xff\x2f\0", 1, 4, of) != 4)
  477.     {
  478.       perror(outputname);
  479.       return NOTREPAIRABLE;  // disk full
  480.     }
  481.     return CHANGED;
  482.   }
  483.   return (!repairedcmd && len == 0) ? NOTCHANGED : CHANGED;
  484. }
  485.  
  486. int midifix(FILE* f, FILE* of)
  487. {
  488. unsigned char tag[4];
  489. long taglen, tagpos, nextpos, midipos, mtrkpos, endpos, otagpos = 0, otaglen;
  490. int trackcnt, tracks = -1;
  491. int changed = 0;
  492.  
  493.   fseek(f, 0, SEEK_END);
  494.   filesize = ftell(f);
  495.   fseek(f, 0, SEEK_SET);
  496.  
  497.   tagpos = findmidi(f);
  498.   if (tagpos < 0)
  499.   {
  500.     printf( "%s: No midi data found.\n", name);
  501.     return NOTREPAIRABLE;
  502.   }
  503.   if (tagpos > 0)
  504.     changed = 1;
  505.   fseek(f, tagpos, SEEK_SET);
  506.   if (fread(tag, 1, 4, f) != 4 || !IsHeaderTag(tag))
  507.   {
  508.     printf( "%s: not a midifile (missing %s)\n", name, mthd);
  509.     return NOTREPAIRABLE;
  510.   }
  511.  
  512.   fseek(f, tagpos+8, SEEK_SET);
  513.   version = getword(f);
  514.   trackcnt = getword(f);
  515.   fseek(f, tagpos+4, SEEK_SET);
  516.   do {
  517.     int validtag = (IsHeaderTag(tag) || IsTrackTag(tag));
  518.  
  519.     if (validtag)
  520.     {
  521.       taglen = getlong(f);
  522.       if (taglen < 0)
  523.     break;
  524.       tracks++;           // track0 is assumed header
  525.       printf( "%08lX: tag %4.4s found. length=%08lX\n", ftell(f) - 8, tag, taglen);
  526.     }
  527.     else
  528.       fseek(f, -4, SEEK_CUR);
  529.  
  530.     if (IsTag(tag, snio))
  531.     {
  532.       memcpy(tag, mthd, 4);
  533.       changed = 1;
  534.       printf("%s: SNio changed to MThd\n", name);
  535.     }
  536.     int lasttrack = 0;
  537.     midipos = findmidi(f);
  538.     if (validtag)
  539.       endpos = findtrackend(f);
  540.     else
  541.       endpos = -1;
  542.     mtrkpos = findtrack(f);
  543.     if (mtrkpos >= 0 && midipos >= 0 && midipos < mtrkpos)
  544.       mtrkpos = -1;
  545.     if (endpos >= 0 && midipos >= 0 && midipos < endpos)
  546.       endpos = -1;
  547.     if (!validtag && mtrkpos < 0)
  548.       break;
  549.     if (validtag && mtrkpos < 0)
  550.       lasttrack = 1;
  551.  
  552.     nextpos = mtrkpos;
  553.     if (midipos >= 0 && (nextpos > midipos || nextpos < 0))
  554.       nextpos = midipos;
  555.     if (endpos >= 0 && (nextpos > endpos || nextpos < 0))
  556.       nextpos = endpos;
  557.  
  558.     if (nextpos < 0)
  559.     {
  560.       nextpos = filesize;
  561.       lasttrack = 1;
  562.     }
  563.     if (midipos >= 0 && nextpos == midipos)
  564.       lasttrack = 1;
  565.  
  566.     if (validtag)
  567.     {
  568.       fseek(f, tagpos, SEEK_SET);
  569.       if (taglen != nextpos - tagpos - 8)
  570.       {
  571.     changed = 1;
  572.     taglen = nextpos - tagpos - 8;
  573.     if (tracks == 0)
  574.       printf( "length of header updated\n");
  575.     else
  576.       printf( "length of track %d updated\n", tracks);
  577.       }
  578.       otagpos = ftell(of);
  579.       if (fwrite(tag, 1, 4, of) != 4) // tag id
  580.       return NOTREPAIRABLE;
  581.       putlong(of, taglen);
  582.       fseek(f, 8, SEEK_CUR);
  583.       if (tracks == 0)
  584.     copybytes(of, f, taglen);
  585.       else
  586.       {
  587.     int trackchanged = copytrack(of, f, taglen);
  588.     if (trackchanged == NOTREPAIRABLE)
  589.       return NOTREPAIRABLE;
  590.     if (trackchanged == CHANGED)
  591.       changed = 1;
  592.       }
  593.       otaglen = ftell(of) - otagpos - 8;
  594.       fseek(of, otagpos+4, SEEK_SET);
  595.       putlong(of, otaglen);
  596.       fseek(of, otagpos + otaglen + 8, SEEK_SET);
  597.     }
  598.     else
  599.     {
  600.       if (lasttrack)
  601.     break;
  602.       changed = 1;
  603.       printf( "remove garbage at end of track (%ld byte%s)\n",
  604.     nextpos-ftell(f), nextpos-ftell(f) == 1 ? "" : "s" );
  605.     }
  606.  
  607.     fseek(f, nextpos, SEEK_SET);
  608.     tagpos = nextpos;
  609.     if (lasttrack)
  610.       break;
  611.   } while (fread(tag, 1, 4, f) == 4);
  612.  
  613.   if (tracks != trackcnt)
  614.   {
  615.     if (tracks < trackcnt)
  616.       printf( "%d track%s missing\n", trackcnt-tracks, trackcnt-tracks==1 ? "" : "s");
  617.     else // tracks > trackcnt
  618.       printf( "%d new track%s found\n", tracks-trackcnt, tracks-trackcnt==1 ? "" : "s");
  619.     updatetrackcount(of, tracks);
  620.     changed = 1;
  621.   }
  622.   int extracount = 0;
  623.   while (ftell(f) < filesize)
  624.   {
  625.     midipos = findmidi(f);
  626.     if (midipos < 0)
  627.       nextpos = filesize;
  628.     else
  629.       nextpos = midipos;
  630.     if (ftell(f) < nextpos)
  631.     {
  632.       changed = 1;
  633.       printf( "remove garbage at end of midi (%ld byte%s)\n",
  634.     nextpos-ftell(f), nextpos-ftell(f) == 1 ? "" : "s" );
  635.     }
  636.     fseek(f, nextpos, SEEK_SET);
  637.     if (midipos < 0)
  638.       break;
  639.     // MThd found
  640.     fgetc(f);
  641.     nextpos = findmidi(f);
  642.     if (nextpos < 0)
  643.       nextpos = filesize;
  644.  
  645.      fseek(f, midipos, SEEK_SET);
  646.     // save probably midi as new file
  647.     extracount++;
  648.     char extraname[14];
  649.  
  650.     sprintf(extraname, extramidi, extracount);
  651.     printf( "Extra midi file found. Written to %s\n", extraname);
  652.     FILE* ef = fopen(extraname, WRITE_BINARY);
  653.     if (!ef)
  654.     {
  655.       perror(extraname);
  656.       return NOTREPAIRABLE;
  657.     }
  658.     else
  659.     {
  660.       changed = 1;
  661.       copybytes(ef, f, nextpos - midipos);
  662.       fclose(ef);
  663.     }
  664.   }
  665.   return changed ? CHANGED : NOTCHANGED;
  666. }
  667.  
  668. int midifix(char* filename, char* outputname)
  669. {
  670.   FILE* f = fopen(filename, READ_BINARY);
  671.   if (!f)
  672.   {
  673.     perror(filename);
  674.     return 1;
  675.   }
  676.   FILE* of = fopen(outputname, WRITE_BINARY);
  677.   if (!of)
  678.   {
  679.     perror(outputname);
  680.     return 1;
  681.   }
  682.   int changed = midifix(f, of);
  683.   fclose(f);
  684.   fclose(of);
  685.   if (changed == NOTREPAIRABLE || changed == NOTCHANGED)
  686.   {
  687.     remove(outputname);
  688.     if (changed == NOTCHANGED)
  689.     {
  690.       printf( "%s is ok.\n", filename);
  691.       return 0;
  692.     }
  693.     return 1;
  694.   }
  695.   printf( "output written to %s\n", outputname);
  696.   return 0;
  697. }
  698.  
  699. // checks and fixes chunk table of midi file
  700. int main(int argc, char** argv)
  701. {
  702.   argc--; argv++;
  703.   while (argc > 0 && **argv == '-')
  704.   {
  705.     if (strncmp(*argv, "-tracksonly", 2) == 0)
  706.     {
  707.       tracksonly = 1;
  708.       argc--; argv++; continue;
  709.     }
  710.     fprintf(stderr, "Invalid option %s.\n", *argv);
  711.     argc--; argv++;
  712.   }
  713.  
  714.   if (!argc)
  715.   {
  716.     printf( "usage: midifix [-tracksonly] filename.mid [result.mid]\n");
  717.     printf("-tracksonly\tcheck and repair only track headers\n");
  718.     printf( "if output filename is not given and changes are necessary then\n");
  719.     printf( "output will be written to midifix.mid\n");
  720.     return 1;
  721.   }
  722.   name = *argv++; argc--;
  723.  
  724.   if (argc > 0)
  725.     outputname = *argv;
  726.   else
  727.     outputname = DEFAULT_OUTPUTNAME;
  728.  
  729.   if (STRICMP(name, outputname) == 0)
  730.   {
  731.     if (STRICMP(name, DEFAULT_OUTPUTNAME) == 0)
  732.     {
  733.       printf( "Cannot write to %s. Must specify alternate outputname!\n",
  734.     DEFAULT_OUTPUTNAME);
  735.       return 1;
  736.     }
  737.     printf( "Cannot change input file. Write output to %s!\n", outputname);
  738.     outputname = DEFAULT_OUTPUTNAME;
  739.   }
  740.   return midifix(name, outputname);
  741. }
  742.